﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace DotNetDetour.DetourWays
{
    /// <summary>
    /// inline hook,通过修改函数的前5字节指令为jmp target_addr实现
    /// </summary>
    public unsafe class NativeDetourFor32Bit : IDetour
    {
        internal static Memory Tranpoline = new Memory();
        internal static List<HookInfo> _hookApiInfos = new List<HookInfo>();

        protected virtual IntPtr SolveJmpIns(IntPtr ptr)
        {
            byte* p = (byte*)ptr;

            while (true)
            {
                if (*p == 0xe9)
                {
                    //jmp relativeAddr
                    p = p + *(int*)(p + 1) + 5;
                }
                else
                    break;
            }
            return (IntPtr)p;
        }

        public void Unpatch(HookInfo apiInfo)
        {
            NativeAPI.CopyMemory(apiInfo.HookAddr, apiInfo.OldFunc, apiInfo.NeedSize);
            Tranpoline.Free(apiInfo.OldFunc);
            foreach (var item in apiInfo.Tranpoline)
            {
                Tranpoline.Free(item);
            }
            _hookApiInfos.Remove(apiInfo);
        }

        public void Unpatch()
        {
            for (int i = _hookApiInfos.Count - 1; i >= 0; i--)
            {
                Unpatch(_hookApiInfos[i]);
            }
        }

        protected virtual void MakeJmp(IntPtr from, IntPtr to, bool forceE9Jmp)
        {
            byte* p = (byte*)from;
            *p++ = 0xe9;
            *(int*)p = (int)((ulong)to - (ulong)from - 5);
            ;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="rawMethod">要hook的目标函数</param>
        /// <param name="hookMethod">提供的替换函数</param>
        /// <param name="originalMethod">原始占位函数(可为空)</param>
        /// <returns></returns>
        public virtual HookInfo Patch(
          IntPtr rawMethod,
          IntPtr hookMethod,
          IntPtr originalMethod)
        {
            rawMethod = SolveJmpIns(rawMethod);
            hookMethod = SolveJmpIns(hookMethod);
            if (originalMethod != IntPtr.Zero)
                originalMethod = SolveJmpIns(originalMethod);

            //Debug.WriteLine($"Patch: Point=0x{rawMethod.ToString("X")}");
            //Debug.WriteLine($"HookMethod: Point=0x{hookMethod.ToString("X")}");
            //Debug.WriteLine($"OriginalMethod: Point=0x{originalMethod.ToString("X")}");

            var hookApiInfo = new HookInfo();
            _hookApiInfos.Add(hookApiInfo);

            //从被Hook的原函数到跳自己的函数
            int needSize = (int)LDasm.SizeofMinByte((void*)rawMethod, 5);

            //原函数头部指令+跳转5字节
            //int total_length = needSize + 14;

            //构造调用原函数的代码块
            hookApiInfo.OldFunc = Tranpoline.Alloc(rawMethod);
            //NativeAPI.VirtualProtect(hookApiInfo.OldFunc, (uint)total_length, Protection.PAGE_EXECUTE_READWRITE, out _);

            NativeAPI.CopyMemory(hookApiInfo.OldFunc, rawMethod, needSize);
            MakeJmp(hookApiInfo.OldFunc + needSize, rawMethod + needSize, false);
            //保存原始数据用于恢复
            hookApiInfo.NeedSize = needSize;
            hookApiInfo.HookAddr = rawMethod;

            if (originalMethod != IntPtr.Zero)
            {
                //如果提供了原函数
                NativeAPI.VirtualProtect(originalMethod, 32, Protection.PAGE_EXECUTE_READWRITE, out _);
                //NativeAPI.CopyMemory(originalMethod, hookApiInfo.OldFunc, needSize);
                MakeJmp(originalMethod, hookApiInfo.OldFunc, false);
            }

            NativeAPI.VirtualProtect(rawMethod, (uint)needSize, Protection.PAGE_EXECUTE_READWRITE, out _);
            MakeJmp(rawMethod, hookMethod, true);

            //Debug.WriteLine($"Patch: Point=0x{rawMethod.ToString("X")}");
            //Debug.WriteLine($"HookMethod: Point=0x{hookMethod.ToString("X")}");
            //Debug.WriteLine($"OriginalMethod: Point=0x{originalMethod.ToString("X")}");

            return hookApiInfo;
        }

        public virtual HookInfo Patch(MethodBase rawMethod/*要hook的目标函数*/,
            MethodBase hookMethod/*用户定义的函数，可以调用原始占位函数来实现对原函数的调用*/,
            MethodBase originalMethod/*原始占位函数*/)
        {
            return Patch(rawMethod.GetRawPtr(),
              hookMethod.GetRawPtr(),
              originalMethod == null ? IntPtr.Zero : originalMethod.GetRawPtr());
        }
    }
}